package com.hk.core.redis.aspect;

import com.hk.commons.exceptions.RequiredLockException;
import com.hk.commons.expression.MethodCachedExpressionEvaluator;
import com.hk.commons.util.StringUtils;
import com.hk.core.redis.annotations.RedisLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.expression.spel.support.SimpleEvaluationContext;

import java.util.Objects;

/**
 * <pre>
 *
 * 加 Order 注解，必需要在 事务之前执行(也就是这个Aspect 拦截器要在 {@link org.springframework.transaction.interceptor.TransactionInterceptor} 之前)，事务提交之后，再释放类。
 * {@link org.springframework.transaction.interceptor.TransactionInterceptor} 的Order 默认为 Integer.MAX_VALUE,
 *      先使用redis上锁、再开启事物 、再执行业务代码，再提交事物 ，再释放redis 锁，才能让下一个线程能获取到锁。
 * </pre>
 * <p>
 * {@link RedisLock} aspect
 *
 * @author Kevin
 */
@Aspect
@Order(value = 100)
public class RedisLockInterceptor {

    private final MethodCachedExpressionEvaluator evaluator = new MethodCachedExpressionEvaluator();

    /**
     * 注意: @annotation 只能写在实现类上，写在接口不上生效
     */
    @Pointcut("@annotation(com.hk.core.redis.annotations.RedisLock)")
    public void redisLock() {
    }

    @Around("redisLock()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        if (pjp.getSignature() instanceof MethodSignature signature) {
            var method = signature.getMethod();
            var redisLock = method.getAnnotation(RedisLock.class);
            if (Objects.nonNull(redisLock)) {
                var key = parseExpression(pjp, redisLock);
                var tryLockTime = redisLock.tryLockTime();
                var lock = new com.hk.core.redis.locks.RedisLock(key, redisLock.expireSeconds(), redisLock.renewal());
                boolean isLocked;
                if (tryLockTime == 0) {
                    isLocked = lock.tryLock();
                } else if (tryLockTime < 0) {
                    if (redisLock.interrupt()) {
                        lock.lockInterruptibly();
                    } else {
                        lock.lock();
                    }
                    isLocked = true;
                } else {
                    isLocked = lock.tryLock(tryLockTime, redisLock.tryLockUnit());
                }
                if (!isLocked && redisLock.requiredLock()) {
                    throw new RequiredLockException(key, pjp.getTarget(), method, pjp.getArgs());
                }
                try {
                    return pjp.proceed();
                } finally {
                    //保证释放锁时，事物已提交(在类上加了Order注解)
                    if (isLocked) {
                        lock.unlock();
                    }
                }
            }
        }
        return pjp.proceed();
    }

    private String parseExpression(ProceedingJoinPoint pjp, RedisLock redisLock) {
        var method = ((MethodSignature) pjp.getSignature()).getMethod();
        var key = redisLock.key();
        var parameters = method.getParameters();
        var args = pjp.getArgs();
        // SimpleEvaluationContext 是比较简陋的 context， 不支持Spring Bean 解析，如有需要，可以使用更全的  StandardEvaluationContext,
        // 主要是使用 BeanResolver 从spring 中获取到指定的bean. 而SimpleEvaluationContext 总是返回null
        var builder = SimpleEvaluationContext.forReadWriteDataBinding();
        int argLen = args.length;
        if (argLen > 1) {
            builder.withRootObject(args[0]);
        }
        var context = builder.build();
        for (int i = 0; i < argLen; i++) {
            context.setVariable(parameters[i].getName(), args[i]);
        }
        try {
            key = evaluator.evaluate(key, method, context, String.class);
        } catch (Exception e) {
            // can not parse SPEL,use original key.
        }
        if (StringUtils.isEmpty(key)) {
            key = pjp.getTarget().getClass().getName() + method.getName();
        }
        return key;
    }
}
